package com.sun.filesigndemo.listener;

import com.sun.filesigndemo.common.utils.DateUtil;
import com.sun.filesigndemo.common.utils.RequestUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Map;

/**
 * <p>用户访问日志过滤器，记录到文件，并且每天一个。</p>
 * <p>记录的信息包括：访问时间（yyyy-MM-dd HH:mm:ss.SSS）、IP、User-Agent、请求方法、服务器响应状态、服务器响应时间、访问地址、各种参数</p>
 * <p>数据格式示例：2015-01-01 00:00:00.000|192.168.1.20|UA|GET|200|10|/winguo!search.do|kw=音乐|pt=1|types=1=2=3</p>
 * <p>数据格式示例说明：数据之间会以竖线 | 分隔，url和其参数之间以及参数与参数之间也都用竖线 | 分隔。
 * 参数名和参数值之间用等号 = 分隔，如果对应多个参数值，则参数值之间用等号 = 分隔</p>
 * @author winkey
 */
@WebFilter(filterName="accessLogFilter",urlPatterns="/*")
public class AccessLogFilter implements Filter
{
    private static Logger log = LoggerFactory.getLogger(AccessLogFilter.class);

    /**
     * 专门用于记录用户访问的日志对象
     */
    private static Logger accessLog = LoggerFactory.getLogger("AccessLog");
    
    @Override
    public void init(FilterConfig config) throws ServletException
    {
        log.info("加载用户访问日志过滤器");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException
    {
        String date = DateUtil.format(new Date(), DateUtil.FMT_DATETIMES);
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse)res;
        String method = request.getMethod();
        String url = request.getServletPath();
        String ip = RequestUtil.getRemoteAddr(request);
        String userAgent = getHeader(request, "User-Agent");

        StringBuilder msg = new StringBuilder(200);
        msg.append(date).append("|").append(ip).append("|").append(encode(userAgent)).append("|").append(method).append("|");

        long start = System.currentTimeMillis();
        try
        {
            chain.doFilter(request, response);
            msg.append(response.getStatus());
        }
        catch (Throwable e)
        {
            msg.append(HttpStatus.INTERNAL_SERVER_ERROR);
            throw new ServletException(e);
        }
        finally
        {
            msg.append("|").append(System.currentTimeMillis() - start).append("|").append(encode(url));
            appendParams(msg, request);
            accessLog.info(msg.toString());
        }
    }

    private String getHeader(HttpServletRequest request, String key)
    {
        String value = request.getHeader(key);
        try
        {
            return value == null ? "" : new String(value.getBytes("ISO-8859-1"), "utf-8");
        }
        catch (UnsupportedEncodingException e)
        {
            throw new IllegalStateException(e);
        }
    }

    private void appendParams(StringBuilder msg, HttpServletRequest request)
    {
        Map<String, String[]> paramMap = request.getParameterMap();

        for (String key : paramMap.keySet())
        {
            msg.append("&").append(encode(key));
            for (String value : paramMap.get(key))
            {
                msg.append("=").append(encode(value));
            }
        }
    }

    /**
     * 对字符串编码
     * @param s 字符串
     * @return 编码后的字符串
     */
    private String encode(String s)
    {
        StringBuilder result = new StringBuilder(s.length() + 5);
        for (int i = 0; i < s.length(); i++)
        {
            char c = s.charAt(i);
            switch (c)
            {
                case '\\':
                case '|':
                case '=':
                    result.append('\\').append(c);
                    break;
                case '\r':
                    result.append("\\r");
                    break;
                case '\n':
                    result.append("\\n");
                    break;
                default:
                    result.append(c);
            }
        }
        return result.toString();
    }

    @Override
    public void destroy()
    {
    }
}
